// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: daniel

#include "coin2/exchange/lmax_digital/api_order/fix/fix_message_builder.h"

#include <utility>

#include <glog/logging.h>

#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/base/symbology/product.h"
#include "coin2/exchange/base/symbology/product_info.h"
#include "coin2/exchange/lmax_digital/symbology/product.h"
#include "quickfix/fix42/NewOrderSingle.h"
#include "quickfix/fix42/OrderCancelRequest.h"
#include "quickfix/fix42/OrderStatusRequest.h"

namespace coin2::exchange::lmax_digital::api_order::fix {

LmaxdigitalOrder::LmaxdigitalOrder(
    const OrderExecution& ord_exec,
    GetProductHolderFunc get_product_holder) {
  Parse(ord_exec, get_product_holder);
  Validate();
}

void LmaxdigitalOrder::Parse(
    const OrderExecution& ord_exec,
    GetProductHolderFunc get_product_holder) {
  CHECK(ord_exec.order_spec) << "ord_exec.order_spec is NULL!";
  using OrderType = coin2::exchange::base::order::OrderType;

  using LmaxdigitalProduct = coin2::exchange::lmax_digital::symbology::LmaxdigitalProduct;
  auto product = LmaxdigitalProduct::FromStr(
      ord_exec.order_spec->product,
      ord_exec.order_spec->order_created_time);
  auto product_holder = get_product_holder(product, ord_exec.order_spec->order_created_time);
  auto formatter = product_holder->product_info().order_formatter();

  client_id = std::to_string(ord_exec.proc_order_id);
  symbol = ord_exec.order_spec->product;
  quantity = stod(formatter.FormatQty(ord_exec.order_spec->qty));
  price = stod(formatter.FormatPrice(ord_exec.order_spec->price));
  type = ord_exec.order_spec->order_type == OrderType::MARKET_ORDER ? MARKET : LIMIT;
  side = ord_exec.order_spec->order_side == 1 ? BUY : SELL;
  handlinst = AUTOMATED_EXECUTION_NO_INTERVENTION;

  if (ord_exec.order_spec->duration == coin::proto::OrderDuration::FOK_ORDER) {
    time_in_force = FILL_OR_KILL;
  } else if (ord_exec.order_spec->duration == coin::proto::OrderDuration::IOC_ORDER) {
    time_in_force = IMMEDIATE_OR_CANCEL;
  } else {
    time_in_force = GOOD_TILL_CANCEL;
  }
}

void LmaxdigitalOrder::Validate() const {
  // TODO(daniel) - validate fields
  if (MARKET == type && GOOD_TILL_CANCEL == time_in_force) {
    CHECK(false) << "GTC order is unsupported by market order";
  }
}

auto FixMessageBuilder::GeneratePlaceOrder(
    const OrderExecution& ord_exec,
    GetProductHolderFunc get_product_holder) -> RequestTypePtr {
  LmaxdigitalOrder order{ord_exec, get_product_holder};
  RequestTypePtr message = std::make_unique<FIX42::NewOrderSingle>();
  message->setField(FIX::HandlInst(order.handlinst));
  message->setField(FIX::ClOrdID(ProcessClientOrderID(ord_exec.proc_order_id)));
  message->setField(FIX::Symbol(order.symbol));
  message->setField(FIX::Side(order.side));
  message->setField(FIX::OrderQty(order.quantity));
  message->setField(FIX::OrdType(order.type));
  message->setField(FIX::TimeInForce(order.time_in_force));
  if (MARKET == order.type) {
    // pass
  } else if (LIMIT == order.type) {
    message->setField(FIX::Price(order.price));
  }
  message->setField(FIX::TransactTime(FIX::UtcTimeStamp{}, 6));

  return message;
}

auto FixMessageBuilder::GenerateCancelOrder(const OrderExecution& ord_exec) -> RequestTypePtr {
  const auto& order_context = *ord_exec.oc;
  const auto& order_spec = order_context.order_spec();
  const std::string client_id_str = ProcessClientOrderID(order_context.proc_order_id());
  const std::string symbol = order_spec.product;
  const int order_side = order_spec.order_side == 1 ? BUY : SELL;
  RequestTypePtr message = std::make_unique<FIX42::OrderCancelRequest>();
  message->setField(FIX::ClOrdID(LmaxdigitalOrder::GenerateRequestId()));
  message->setField(FIX::OrigClOrdID(client_id_str));
  message->setField(FIX::Side(order_side));
  message->setField(FIX::Symbol(symbol));
  message->setField(FIX::TransactTime(FIX::UtcTimeStamp{}, 6));

  return message;
}

std::string FixMessageBuilder::ProcessClientOrderID(int64_t proc_order_id) {
  if (0 == proc_order_id) {
    return std::string{};
  }
  return std::to_string(proc_order_id);
}

}  // namespace coin2::exchange::lmax_digital::api_order::fix
